﻿using System;

namespace MWS.Processing.Processors
{
    /// <summary>
    /// </summary>
    /// <typeparam name = "TRequest"></typeparam>
    /// <typeparam name = "TResponse"></typeparam>
    public abstract class OperationProcessor<TRequest, TResponse> : Processor<TRequest>
        where TRequest : class
    {
        /// <summary>
        /// </summary>
        /// <param name = "operation">The operation used to process requests. This needs to have an input type of TRequest
        ///   and an output type of TResponse.</param>
        protected OperationProcessor(Func<TRequest, TResponse> operation)
        {
            // Set the operation to execute in the processes loop.
            Operation = operation;

            // Wire up the process event handler.
            Process += OnProcess;

            // Wire up the process complete event handler.
            ProcessComplete += OnProcessComplete;
        }

        /// <summary>
        ///   This will actually execute the operation provided in the constuctor. It will take the request from the 
        ///   event arguments and use that as an input to the operation. Override this method if you want to customize
        ///   the behavior (if you want to do anything other than just invoke the operation).
        /// </summary>
        /// <param name = "sender">The sender of the process event.</param>
        /// <param name = "args">The event arguments, which includes the request to process.</param>
        public virtual void OnProcess(object sender, RequestEventArgs args)
        {
            try
            {
                // Try invoking the operation with the request provided in the event arguments.
                var response = Operation.Invoke(args.Request);
                // Raise the post process event with the response, if a handler is registered.
                if (ProcessComplete != null)
                    // Send the post process event with the request and response.
                    ProcessComplete(this, new ResponseEventArgs {Request = args.Request, Response = response});
            }
            catch (Exception exception)
            {
                // If there is a event handler registered for the error event, fire the event.
                if (Error != null)
                    // Send the request that cause the error and the exception found as part of the arguments.
                    Error(this, new ErrorEventArgs {Request = args.Request, Exception = exception});
                    // If no event handler is registered, then just let the error bubble up.
                else
                    throw;
            }
        }

        /// <summary>
        ///   Implement this method to handle the results of a processing complete event.
        /// </summary>
        /// <param name = "sender">The sender of the event.</param>
        /// <param name = "args">The processing complete event args (the request and response)</param>
        public abstract void OnProcessComplete(object sender, ResponseEventArgs args);

        /// <summary>
        ///   This is the operation to execute when requests are pulled from the queue. As requests
        ///   are pulled from the queue they are given to this operation as inputs when it executes.
        /// </summary>
        internal readonly Func<TRequest, TResponse> Operation;


        /// <summary>
        ///   Delegate for a handler of a response event.
        /// </summary>
        /// <param name = "sender">The sender of the event.</param>
        /// <param name = "args">The arguments for event, the response and the request.</param>
        public delegate void ResponseHandler(object sender, ResponseEventArgs args);

        /// <summary>
        ///   Delegate for a handler of an error event.
        /// </summary>
        /// <param name = "sender">The sender of the error.</param>
        /// <param name = "args">The arguments of the error.</param>
        public delegate void ErrorHandler(object sender, ErrorEventArgs args);


        /// <summary>
        ///   This event is fired when an excpetion happens, if a handler is registered to listen for this event. If
        ///   no handler is registered then the event is not fired, instead exceptions are allowed to bubble up.
        /// </summary>
        public event ErrorHandler Error;


        /// <summary>
        ///   This event if fired after a processing completes. This will contain the request and the response of the operation
        ///   executed.
        /// </summary>
        public event ResponseHandler ProcessComplete;


        /// <summary>
        ///   Event arguments for the case when an error occured while processing a request.
        /// </summary>
        public class ErrorEventArgs : RequestEventArgs
        {
            /// <summary>
            ///   The exception thrown when processing the attached requests.
            /// </summary>
            public Exception Exception { get; set; }
        }

        /// <summary>
        ///   Event arguments used when a response happens.
        /// </summary>
        public class ResponseEventArgs : RequestEventArgs
        {
            /// <summary>
            ///   The response, the result of executing the operation.
            /// </summary>
            public TResponse Response { get; set; }
        }
    }
}